/*
	gen_pattern.cc	Game Pattern Generator
	Copyright (c) 2001, 2002, 2003, 2004, 2005 Kriang Lerdsuwanakij
	email:		lerdsuwa@users.sourceforge.net

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "config.h"

#include <fstream>
#include <sstream>

#include <math.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>

#include "pattern.h"
#include "log_proc.h"

#include "gtstream.h"

#ifdef _
#undef _
#endif

#ifdef N_
#undef N_
#endif

#include <libintl.h>
#define _(x) gettext(x)
#define N_(x) x

// Grab version number in VERSION variable
// Use same version number as main GRhino program
#undef VERSION
const char *
#include "scripts/version"
;

const char *prog_name = "gen_pattern";
const char *prog_ver = VERSION;

struct pattern_state {
	pattern_t p;

			// Pattern information
			//   pat[PATTERN][MOVE_INDEX][PATTERN_INDEX]
			// where
			//   PATTERN = ROW1, ROW2, etc.
			//   MOVE_INDEX = 0, 1, ..., num_move_index-1
			//   PATTERN_INDEX = 0, 1, ... according to
			//     positions of black and white pieces
	raw_pattern_info ***pat;
	pattern_state(pattern_t p_, raw_pattern_info ***pat_)
		: p(p_), pat(pat_) {}
};

/* Calculate score based on
     b = number of black wins
     w = number of white wins
   Result truncated to fit signed char.  */

signed char	log_func(long b, long w)
{
	if (b == w)	// Special case to avoid round-off problem
		return 0;

	long	a;	// Make sure we don't put too high score for case
			// like win-loss = 1-0
	if (b+w < 5)
		a = 5 - b - w;
	else
		a = 1;	// Make formula work even when either b or w is zero


	long	x;
			// Make sure we don't have rounding non-symmetry
	if (b > w)
		x = static_cast<long>(10*log(static_cast<double>(b+a)/(w+a)));
	else
		x = -static_cast<long>(10*log(static_cast<double>(w+a)/(b+a)));

			// Limit value to fit signed char
	if (x > 7)
		x = 7;
	else if (x < -7)
		x = -7;
	return static_cast<signed char>(x);
}

void	init_file(pattern_t p)
{
				// Special marker for all patterns
	if (p == PATTERN_UNKNOWN) {
		for (int p = 0; p != PATTERN_UNKNOWN; p++)
			init_file(static_cast<pattern_t>(p));
		return;
	}

	int handle = creat(get_pattern_file(p), S_IRUSR | S_IWUSR);
	if (handle == -1) {
		gtstream bufstr;
		gtout(bufstr, _("cannot create file %$\n"))
			<< get_pattern_file(p);
		throw std::runtime_error(bufstr.str());
	}

				// Loop for all move indexes
	raw_pattern_info **pat = new raw_pattern_info *[num_move_index];
	for (int pos = 0; pos < num_move_index; ++pos) {
		pat[pos] = new raw_pattern_info[get_pattern_size(p)];

				// Loop for all pattern indexes
		for (int i = 0; i < get_pattern_size(p); ++i) {
			pat[pos][i].black_win = 0;
			pat[pos][i].white_win = 0;
		}
	}

				// Loop for all move indexes
	for (int pos = 0; pos < num_move_index; ++pos) {

				// Write all pattern indexes
		int size = sizeof(raw_pattern_info) * get_pattern_size(p);
		if (write(handle, pat[pos], size) != size) {
			close(handle);

			gtstream bufstr;
			gtout(bufstr, _("cannot write file %$\n"))
				<< get_pattern_file(p);
			throw std::runtime_error(bufstr.str());
		}
	}

	for (int pos = 0; pos < num_move_index; ++pos) {
		delete [] pat[pos];
	}
	delete [] pat;

	close(handle);
}

int	num_update[PATTERN_UNKNOWN][num_move_index];
int	update_list[PATTERN_UNKNOWN][num_move_index][60];
int	update_move_index;

void	init_info()
{
	update_move_index = -1;
	for (int p = 0; p != PATTERN_UNKNOWN; ++p)
		for (int i = 0; i < num_move_index; ++i)
			num_update[p][i] = 0;
}

bool	find_update(pattern_t p, int move_index, int pos)
{
	for (int i = 0; i < num_update[p][move_index]; ++i) {
		if (pos == update_list[p][move_index][i])	// Already updated
			return true;
	}
	return false;
}

void	update_info_table(pattern_t p, raw_pattern_info ***pat, int diff,
			  int move_index, int pos1, int pos2, int pos3, int pos4)
{
	if (find_update(p, move_index, pos1))
		return;
	update_list[p][move_index][num_update[p][move_index]] = pos1;
	num_update[p][move_index]++;
	update_list[p][move_index][num_update[p][move_index]] = pos2;
	num_update[p][move_index]++;
	update_list[p][move_index][num_update[p][move_index]] = pos3;
	num_update[p][move_index]++;
	update_list[p][move_index][num_update[p][move_index]] = pos4;
	num_update[p][move_index]++;

			// Find winner
	{
		if (diff > 0)
			pat[p][move_index][pos1].black_win++;
		else if (diff < 0)
			pat[p][move_index][pos1].white_win++;
		else {
			// Draw game
			pat[p][move_index][pos1].black_win++;
			pat[p][move_index][pos1].white_win++;
		}
	}

	if (get_pattern_symmetry(p)) {
		if (diff > 0)
			pat[p][move_index][pos2].black_win++;
		else if (diff < 0)
			pat[p][move_index][pos2].white_win++;
		else {
			pat[p][move_index][pos2].black_win++;
			pat[p][move_index][pos2].white_win++;
		}
	}

					// Need to maintain fairness when
					// comparing positions during searching
	{
		if (diff < 0)
			pat[p][move_index][pos3].black_win++;
		else if (diff > 0)
			pat[p][move_index][pos3].white_win++;
		else {
			pat[p][move_index][pos3].black_win++;
			pat[p][move_index][pos3].white_win++;
		}
	}

	if (get_pattern_symmetry(p)) {
		if (diff < 0)
			pat[p][move_index][pos4].black_win++;
		else if (diff > 0)
			pat[p][move_index][pos4].white_win++;
		else {
			pat[p][move_index][pos4].black_win++;
			pat[p][move_index][pos4].white_win++;
		}
	}
}

void	update_info(pattern_t p, raw_pattern_info ***pat, byte_board_info *board,
		    int diff)
{
				// Special marker for all patterns
	if (p == PATTERN_UNKNOWN) {
		for (int pp = 0; pp != PATTERN_UNKNOWN; pp++)
			update_info(static_cast<pattern_t>(pp), pat,
					board, diff);
		return;
	}

	int move_index = to_move_index(board->get_num_move());

	for (int i = 0; i < get_num_pattern(p); ++i) {

				// Appeared pattern_index
		int pos1 = 0;
		for (int j = 0; j < get_pattern_piece(p); ++j) {
			pos1 += pow_3[j] * to_pattern_index(board->board[
						static_cast<int>(pattern_data[p].pattern[i][j])]);
		}

				// Mirrored pattern_index
		int pos2 = 0;
		for (int j = 0; j < get_pattern_piece(p); ++j) {
			pos2 += pow_3[j] * to_pattern_index(board->board[
						static_cast<int>(pattern_data[p].pattern[i][get_pattern_piece(p)-1-j])]);
		}

				// Color swapped pattern_index
		int pos3 = 0;
		for (int j = 0; j < get_pattern_piece(p); ++j) {
			pos3 += pow_3[j] * to_pattern_index(-board->board[
						static_cast<int>(pattern_data[p].pattern[i][j])]);
		}

				// Mirrored and color swapped pattern_index
		int pos4 = 0;
		for (int j = 0; j < get_pattern_piece(p); ++j) {
			pos4 += pow_3[j] * to_pattern_index(-board->board[
						static_cast<int>(pattern_data[p].pattern[i][get_pattern_piece(p)-1-j])]);
		}

				// Update some poorly represented patterns during
				// early to midgame from late-mid game as well
		if (move_index < 8
		    && (p == PATTERN_ROW1 || p == PATTERN_ROW2
			|| p == PATTERN_EDGE_X || p == PATTERN_CORNER5X2
			|| p == PATTERN_DIAG1 || p == PATTERN_DIAG2)) {

			for (int move_index_ = 0; move_index_ < 8; move_index_++) {
				update_info_table(p, pat, diff, move_index_, pos1, pos2, pos3, pos4);
			}
		}
		else
			update_info_table(p, pat, diff, move_index, pos1, pos2, pos3, pos4);
		
	}
}

void	process_game(pattern_state &t, game_log &game)
{
	byte_board_info board(game.board);

	init_info();

				// Now collect pattern info

				// Loop for all game moves
	for (int i = 0; i < game.num_move_queue; ++i) {
		int player = game.move_queue_color[i];
		int pos = game.move_queue[i];

				// Dump core upon broken game record
		if (!board.can_play(player, pos))
			throw std::runtime_error(_("invalid game moves"));
		board.place_piece(player, pos);

				// Update game info using final scores
		update_info(t.p, t.pat, &board,
			game.black_score-game.white_score);
	}
}

void	load_file(const char *f, pattern_state &t)
{
	pattern_t p = t.p;
	raw_pattern_info ***pat = t.pat;

				// Special marker for all patterns
	if (p == PATTERN_UNKNOWN) {
		for (int pp = 0; pp != PATTERN_UNKNOWN; pp++) {
			pattern_state tt(static_cast<pattern_t>(pp), pat);
			load_file(f, tt);
		}
		return;
	}

	int handle = open(get_pattern_file(p), O_RDONLY| O_BINARY);
	if (handle == -1) {
		gtstream bufstr;
		gtout(bufstr, _("cannot open file %$\n"))
			<< get_pattern_file(p);
		throw std::runtime_error(bufstr.str());
	}

	int io_size = sizeof(raw_pattern_info) * get_pattern_size(p);

	pat[p] = new raw_pattern_info *[num_move_index];

				// Loop for all move indexes
	for (int pos = 0; pos < num_move_index; ++pos) {

				// Read all pattern indexes
		pat[p][pos] = new raw_pattern_info[get_pattern_size(p)];
		if (read(handle, pat[p][pos], io_size) != io_size) {
			close(handle);

			gtstream bufstr;
			gtout(bufstr, _("cannot read file %$\n"))
				<< get_pattern_file(p);
			throw std::runtime_error(bufstr.str());
		}
	}
	close(handle);
}

void	store_file(const char *f, pattern_state &t)
{
	pattern_t p = t.p;
	raw_pattern_info ***pat = t.pat;

				// Special marker for all patterns
	if (p == PATTERN_UNKNOWN) {
		for (int pp = 0; pp != PATTERN_UNKNOWN; pp++) {
			pattern_state tt(static_cast<pattern_t>(pp), pat);
			store_file(f, tt);
		}
		return;
	}

	int io_size = sizeof(raw_pattern_info) * get_pattern_size(p);

	int handle = creat(get_pattern_file(p), S_IRUSR | S_IWUSR);
	if (handle == -1) {
		gtstream bufstr;
		gtout(bufstr, _("cannot create file %$\n"))
			<< get_pattern_file(p);
		throw std::runtime_error(bufstr.str());
	}

				// Loop for all move indexes
	for (int pos = 0; pos < num_move_index; ++pos) {

				// Write all pattern indexes
		if (write(handle, pat[p][pos], io_size) != io_size) {
			close(handle);

			gtstream bufstr;
			gtout(bufstr, _("cannot write file %$\n"))
				<< get_pattern_file(p);
			throw std::runtime_error(bufstr.str());
		}
	}

	close(handle);
}

void	generate_file(const char *f, pattern_state &t)
{
	pattern_t p = t.p;
	raw_pattern_info ***pat = t.pat;

				// Special marker for all patterns
	if (p == PATTERN_UNKNOWN) {
		for (int pp = 0; pp != PATTERN_UNKNOWN; pp++) {
			pattern_state tt(static_cast<pattern_t>(pp), pat);
			generate_file(f, tt);
		}
		return;
	}

	int handle = creat(get_pattern_data_file(p), S_IRUSR | S_IWUSR);
	if (handle == -1) {
		gtstream bufstr;
		gtout(bufstr, _("cannot create file %$\n"))
			<< get_pattern_data_file(p);
		throw std::runtime_error(bufstr.str());
	}

	int size = get_pattern_size(p);
	pattern_info **pat2 = new pattern_info *[num_move_index];
	for (int pos = 0; pos < num_move_index; ++pos) {
		pat2[pos] = new pattern_info[size];
		for (int i = 0; i < size; ++i) {
			pat2[pos][i] = log_func(
					pat[p][pos][i].black_win,
					pat[p][pos][i].white_win);
		}
	}

				// Set default value according to
				// number of corners
	for (int pos = 1; pos < num_move_index-1; ++pos) {
		for (int i = 0; i < size; ++i) {
			if (pat[p][pos][i].black_win + pat[p][pos][i].white_win == 0) {
				int pat2i = 0;
				if (p == PATTERN_ROW1) {
					pat2i += 64 * extract_color(i, 0);
					pat2i += 64 * extract_color(i, 7);
				}
				else if (p == PATTERN_DIAG1) {
					pat2i += 64 * extract_color(i, 0);
					pat2i += 64 * extract_color(i, 7);
				}
				else if (p == PATTERN_EDGE_X) {
					pat2i += 64 * extract_color(i, 1);
					pat2i += 64 * extract_color(i, 8);
				}
				else if (p == PATTERN_CORNER5X2) {
					pat2i += 64 * extract_color(i, 0);
				}

				if (pat2i > 127)
					pat2[pos][i] = 127;
				else if (pat2i < -127)
					pat2[pos][i] = -127;
				else
					pat2[pos][i] = pat2i;
			}
		}
	}

				// Interpolate
	for (int pos = 1; pos < num_move_index-1; ++pos) {
		for (int i = 0; i < size; ++i) {
			if (pat[p][pos][i].black_win + pat[p][pos][i].white_win == 0
			    && pat2[pos][i] == 0) {
				int pos_a = pos-1;
				int pos_b = pos+1;
				while (pos_a > 0 && pat2[pos_a][i] == 0 &&
				       pat[p][pos_a][i].black_win + pat[p][pos_a][i].white_win == 0)
					pos_a--;
				while (pos_b < num_move_index-1 && pat2[pos_b][i] == 0 &&
				       pat[p][pos_b][i].black_win + pat[p][pos_b][i].white_win == 0)
					pos_b++;

				// Should not suffer symmetry problem during
				// division
				pat2[pos][i] = ((pos-pos_a)*pat2[pos_b][i]
						+ (pos_b-pos)*pat2[pos_a][i])
					       /(pos_b-pos_a);
			}
		}
	}

				// Compress table
	int comp_size = size/3*2;
	for (int pos = 0; pos < num_move_index; ++pos) {

					// Verify table symmetry
		for (int j = comp_size, k = size/3-1; j < size; ++j, --k) {
			if (pat2[pos][j] != -pat2[pos][k]) {
/*				std::cout << "p=" << p << " pos=" << pos << " j=" << j << " k=" << k
				<< " : " << int(pat2[pos][j]) << ' ' << int(pat2[pos][k]);
				show_pattern(p, j);
				std::cout << " : ";
				show_pattern(p, k);
				std::cout << ' ' << pat[p][pos][j].black_win;
				std::cout << ' ' << pat[p][pos][j].white_win;
				std::cout << ' ' << pat[p][pos][k].black_win;
				std::cout << ' ' << pat[p][pos][k].white_win;
				std::cout << '\n';*/
				throw std::runtime_error(_("pattern symmetry error"));
			}
		}
		if (write(handle, pat2[pos], comp_size) != comp_size) {
			close(handle);

			gtstream bufstr;
			gtout(bufstr, _("cannot write file %$\n"))
				<< get_pattern_data_file(p);
			throw std::runtime_error(bufstr.str());
		}
	}

	for (int pos = 0; pos < num_move_index; ++pos) {
		delete [] pat2[pos];
	}
	delete [] pat2;

	close(handle);
}

void	process_pattern(pattern_t pattern, int argc, char *argv[])
{
	bool	init_pattern = false;

	raw_pattern_info ***pat = 0;

	for (int i = 2; i < argc; ++i) {
		if (strcmp(argv[i], "init") == 0) {
						// Create new file with empty
						// pattern data

			init_file(pattern);	// Write empty pattern data
			init_pattern = false;	// Force reload
		}
		else if (strcmp(argv[i], "gen") == 0) {
						// Generate data file suitable
						// for GRhino

						// Pattern data not yet initialized
			if (init_pattern == false) {
				delete [] pat;
				pat = new raw_pattern_info **[PATTERN_UNKNOWN];
				for (int pp = 0; pp != PATTERN_UNKNOWN; pp++)
					pat[pp] = 0;
			}
			pattern_state t(pattern, pat);
			if (init_pattern == false) {
				load_file(argv[i], t);	// Only load once
				init_pattern = true;
			}

			generate_file(argv[i], t);
		}
		else {
						// Command line argument is
						// a filename

						// Pattern data not yet initialized
			if (init_pattern == false) {
				delete [] pat;
				pat = new raw_pattern_info **[PATTERN_UNKNOWN];
				for (int pp = 0; pp != PATTERN_UNKNOWN; pp++)
					pat[pp] = 0;
			}
			pattern_state t(pattern, pat);
			if (init_pattern == false) {
				load_file(argv[i], t);	// Only load once
				init_pattern = true;
			}

			process_file<game_log>(argv[i], t);
			store_file(argv[i], t);		// Store after every processed
							// file
		}
	}

							// Free allocated memory
	if (pat) {
		for (int pp = 0; pp != PATTERN_UNKNOWN; pp++) {
					// In case we are processing only one
					// pattern
			if (pat[pp]) {
				for (int pos = 0; pos < num_move_index; ++pos) {
					delete [] pat[pp][pos];
				}
				delete [] pat[pp];
			}
		}
	}
	delete [] pat;
}

int	main_real(int argc, char *argv[])
{
	use_private_files(true);
	if (argc == 1) {
		gtout(std::cout, _("%$ %$ - Pattern generator\n")) << prog_name << prog_ver;
		std::cout << _("(c) 2001, 2002, 2003, 2004, 2005 Kriang Lerdsuwanakij\n\n");
		gtout(std::cout, _("Usage:   %$ PATTERN init|gen|FILES\n\n")) << prog_name;
		return 0;
	}
	else if (argc < 3)
		throw std::runtime_error(_("bad arguments"));

	pattern_t pattern = PATTERN_UNKNOWN;
	if (strcmp(argv[1], "row1") == 0) {
		pattern = PATTERN_ROW1;
	}
	else if (strcmp(argv[1], "row2") == 0) {
		pattern = PATTERN_ROW2;
	}
	else if (strcmp(argv[1], "row3") == 0) {
		pattern = PATTERN_ROW3;
	}
	else if (strcmp(argv[1], "row4") == 0) {
		pattern = PATTERN_ROW4;
	}
	else if (strcmp(argv[1], "diag1") == 0) {
		pattern = PATTERN_DIAG1;
	}
	else if (strcmp(argv[1], "diag2") == 0) {
		pattern = PATTERN_DIAG2;
	}
	else if (strcmp(argv[1], "diag3") == 0) {
		pattern = PATTERN_DIAG3;
	}
	else if (strcmp(argv[1], "diag4") == 0) {
		pattern = PATTERN_DIAG4;
	}
	else if (strcmp(argv[1], "diag5") == 0) {
		pattern = PATTERN_DIAG5;
	}
	else if (strcmp(argv[1], "edge-x") == 0) {
		pattern = PATTERN_EDGE_X;
	}
	else if (strcmp(argv[1], "corner5x2") == 0) {
		pattern = PATTERN_CORNER5X2;
	}
	else if (strcmp(argv[1], "all") == 0) {
		;
	}
	else {
		throw std::runtime_error(_("bad pattern name"));
	}

	process_pattern(pattern, argc, argv);
	return 0;
}

int	main(int argc, char *argv[])
{
	try {
		return main_real(argc, argv);
	}
	catch (std::exception &e) {
		std::cout << std::flush;
		gtout(std::cerr, _("%$: %$\n")) << prog_name << e.what();
	}
	catch (const char *s) {
		std::cout << std::flush;
		gtout(std::cerr, _("%$: exception %$\n")) << prog_name << s;
	}
	catch (...) {
		std::cout << std::flush;
		gtout(std::cerr, _("%$: unknown exception\n")) << prog_name;
	}
	return 1;
}
